package game;

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.swing.JApplet;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;

@SuppressWarnings( { "serial", "unused" })
public class FIApplet extends JApplet implements KeyListener, MouseListener {
	private JLabel canvas;
	private FILocal fiLocal;
	private BufferedImage bg;
	private Color[] palette;
	private Font defFont;
	private byte[] slot; // セーブデータの一時保存場所
	private int savePos; // セーブデータの保存先アドレス
	public String saveString = "!!no savedata"; // セーブデータのbase64符号化文字列
	private Map<Integer, Integer> keyEventMap = new HashMap<Integer, Integer>();
	private BufferedImage chips; // チップイメージ
	private Timer timer;
	private List<BufferedImage> mapChips = new ArrayList<BufferedImage>();
	private ByteArrayOutputStream bos;
	private int keyState;
	private SaveDialog saveDialog;

	public void init() {
		setupPalette();
		setupKeyEvent();

		// バッファ用グラフィックの作成
		bg = new BufferedImage(240, 240, BufferedImage.TYPE_INT_RGB);

		// セーブスロットデータの作成
		slot = new byte[FIConst.SAVE_SIZE * 4 + 24];
		for (int i = 0; i < slot.length; i++) {
			slot[i] = 0;
		}

		setSize(240, 240);

		// フォントデータの作成
		defFont = new Font(Font.MONOSPACED, Font.PLAIN, 11);

		getContentPane().setLayout(new BorderLayout());

		// メイン描画コンポーネントの作成
		canvas = new JLabel() {
			public void paintComponent(Graphics g) {
				paintCanvas((Graphics2D) g);
			}
		};
		canvas.addKeyListener(this);
		canvas.setSize(240, 240);
		canvas.setFont(defFont);
		getContentPane().add(canvas);
		canvas.setFocusable(true);
		addMouseListener(this);

		// SaveDialogの作成と登録
		Frame frm;
		Component co = this;
		while(!((co = co.getParent()) instanceof Frame)) {}
		frm = (Frame) co;
		saveDialog = new SaveDialog(frm);

		// タイマーの作成
		timer = new Timer(150, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				tick();
			}
		});
		timer.start();

		fiLocal = new FILocal();
	}

	public void paint(Graphics g) {
		fiLocal.g$draw(g, FIConst.G_REPAINT, 0, 0, 0, null);
	}

	private void paintCanvas(Graphics2D g) {
		g.setClip(0, 0, 240, 240);
		g.drawImage(bg, 0, 0, this);
	}

	private void tick() {
		if (fiLocal != null) {
			fiLocal.processEvent(Display.TIMER_EXPIRED_EVENT, 0);
		}
	}

	@Override
	public void keyPressed(KeyEvent e) {
		int keyCode = e.getKeyCode();
		if (keyState == 0 && keyEventMap.containsKey(keyCode)) {
			fiLocal.processEvent(Display.KEY_PRESSED_EVENT, keyEventMap
					.get(keyCode));
			keyState |= (1 << keyEventMap.get(keyCode));
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		int keyCode = e.getKeyCode();
		if (keyEventMap.containsKey(keyCode)) {
			fiLocal.processEvent(Display.KEY_RELEASED_EVENT, keyEventMap
					.get(keyCode));
			keyState = 0;
		}
	}

	@Override
	public void keyTyped(KeyEvent arg0) {
	}

	/* ======================================================== */
	// ユーティリティメソッド
	/* ======================================================== */

	private void setupPalette() {
		palette = new Color[16];
		palette[0x0] = new Color(0x00, 0x00, 0x00);
		palette[0x1] = new Color(0x00, 0x00, 0xff);
		palette[0x2] = new Color(0x00, 0xff, 0x00);
		palette[0x3] = new Color(0x00, 0xff, 0xff);
		palette[0x4] = new Color(0xff, 0x00, 0x00);
		palette[0x5] = new Color(0xff, 0x00, 0xff);
		palette[0x6] = new Color(0xff, 0xff, 0x00);
		palette[0x7] = new Color(0xff, 0xff, 0xff);
		palette[0x8] = new Color(0x80, 0x80, 0x80);
		palette[0x9] = new Color(0x00, 0x00, 0x80);
		palette[0xa] = new Color(0x00, 0x80, 0x00);
		palette[0xb] = new Color(0x00, 0x80, 0x80);
		palette[0xc] = new Color(0x80, 0x00, 0x00);
		palette[0xd] = new Color(0x80, 0x00, 0x80);
		palette[0xe] = new Color(0x80, 0x80, 0x00);
		palette[0xf] = new Color(0xc0, 0xc0, 0xc0);
	}

	private void setupKeyEvent() {
		keyEventMap = new HashMap<Integer, Integer>();
		keyEventMap.put(KeyEvent.VK_ENTER, Display.KEY_SELECT);
		keyEventMap.put(KeyEvent.VK_LEFT, Display.KEY_LEFT);
		keyEventMap.put(KeyEvent.VK_UP, Display.KEY_UP);
		keyEventMap.put(KeyEvent.VK_RIGHT, Display.KEY_RIGHT);
		keyEventMap.put(KeyEvent.VK_DOWN, Display.KEY_DOWN);
		keyEventMap.put(KeyEvent.VK_ESCAPE, Display.KEY_IAPP);
		keyEventMap.put(KeyEvent.VK_Z, Display.KEY_IAPP);
		keyEventMap.put(KeyEvent.VK_X, Display.KEY_SELECT);
		keyEventMap.put(KeyEvent.VK_0, 0);
		keyEventMap.put(KeyEvent.VK_1, 1);
		keyEventMap.put(KeyEvent.VK_2, 2);
		keyEventMap.put(KeyEvent.VK_3, 3);
		keyEventMap.put(KeyEvent.VK_4, 4);
		keyEventMap.put(KeyEvent.VK_5, 5);
		keyEventMap.put(KeyEvent.VK_6, 6);
		keyEventMap.put(KeyEvent.VK_7, 7);
		keyEventMap.put(KeyEvent.VK_8, 8);
		keyEventMap.put(KeyEvent.VK_9, 9);
	}

	/* ===================================== */
	// ゲームエンジンのローカライズ
	/* ===================================== */

	class FILocal extends FICanvas {
		/**
		 * オープニング・エンディングデモ画面を描画
		 *
		 * @param g
		 *            描画対象グラフィックオブジェクト
		 */
		protected void paintDemo(Object g) {
			Graphics2D g2d = (Graphics2D) g;
			GradientPaint grad = new GradientPaint(0, 0, new Color(0, 0, 255),
					0, 180, new Color(180 * 3 / 4, 180, 255));
			g2d.setPaint(grad);
			g2d.fillRect(0, 0, 240, 180);

			grad = new GradientPaint(0, 180, new Color(0, 155, 155), 0, 190,
					new Color(0, 255, 255));
			g2d.setPaint(grad);
			g2d.fillRect(0, 180, 240, 210);

			grad = new GradientPaint(0, 210, new Color(25, 255, 255), 0, 230,
					new Color(245, 255, 195));
			g2d.setPaint(grad);
			g2d.fillRect(0, 210, 240, 240);
			super.paintDemo(g2d);
		}

		/**
		 * 半透明黒のウィンドウを描画
		 *
		 * @param g
		 *            描画対象グラフィックオブジェクト
		 * @param wHeight
		 *            ウィンドウの高さ
		 */

		protected void paintWindow(Object g, int wHeight) {
			Graphics2D g2d = (Graphics2D) g;
			Paint p = g2d.getPaint();
			g2d.setComposite(AlphaComposite.getInstance(
					AlphaComposite.SRC_OVER, 0.5f));
			g2d.setColor(Color.BLACK);

			g2d.fillRect(0, 0, getWidth() - 10, wHeight);
			g2d.setPaint(p);
		}

		/**
		 * 背景に虹を描画
		 *
		 * @param g
		 */
		protected void paintRainbow(Object g) {
			Graphics2D g2d = (Graphics2D) g;
			Paint oldPaint = g2d.getPaint();
			g2d.setComposite(AlphaComposite.getInstance(
					AlphaComposite.SRC_OVER, 0.75f));

			int[] rvals = { 255, 255, 000, 000, 000, 255 };
			int[] gvals = { 000, 255, 255, 255, 000, 000 };
			int[] bvals = { 000, 000, 000, 255, 255, 255 };
			int x = 40;

			for (int i = 0; i < 5; i++) {
				GradientPaint grad = new GradientPaint(i * 32 + x, 0,
						new Color(rvals[i], gvals[i], bvals[i]), (i + 1) * 32
								+ x, 0, new Color(rvals[i + 1], gvals[i + 1],
								bvals[i + 1]));
				g2d.setPaint(grad);
				g2d.fillRect(i * 32 + x, 0, 32, 160);
			}
			g2d.setComposite(AlphaComposite.getInstance(
					AlphaComposite.SRC_OVER, 1));
			g2d.setPaint(oldPaint);
		}

		@Override
		protected void g$createImage(byte[] imageData) throws Exception {
			try {
				chips = ImageIO.read(new ByteArrayInputStream(imageData));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		@Override
		protected Object g$draw(Object gg, int command, int a1, int a2, int a3,
				String string) {
			Graphics2D g = (Graphics2D) gg;
			if (command == FIConst.G_DRAW_STRING) {
				g.drawString(string, a1, a2);
			}
			if (command == FIConst.G_DRAW_RECT) {
				g.drawRect(a1, a2, a3, a3);
			}
			if (command == FIConst.G_FILL_RECT) {
				g.fillRect(a1, a2, a3, a3);
			}
			if (command == FIConst.G_TRANSLATE) {
				g.translate(a1, a2);
			}
			if (command == FIConst.G_SET_COLOR) {
				g.setColor(palette[a1]);
			}
			if (command == FIConst.G_DRAW_CHIP) {
				if (chips != null) {
					if (string == null) {
						g.drawImage(chips, a2, a3, a2 + FIConst.CHIP, a3
								+ FIConst.CHIP, (a1 % 10) * FIConst.CHIP,
								(a1 / 10) * FIConst.CHIP, (a1 % 10)
										* FIConst.CHIP + FIConst.CHIP,
								(a1 / 10) * FIConst.CHIP + FIConst.CHIP,
								FIApplet.this);
					} else {
						g.drawImage(mapChips.get(a1), a2, a3, FIApplet.this);
					}
				}
			}
			if (command == FIConst.G_FLIP_BUFFER) {
				g.drawImage(bg, a1, a2, FIApplet.this);
			}
			if (command == FIConst.G_SHIFT_BUFFER) {
				if (a1 < 0) {
					return bg.createGraphics();
				} else {
					return mapChips.get(a1).createGraphics();
				}
			}
			if (command == FIConst.G_COPY_AREA) {
				// 画像全体をずらす
				g.copyArea(0, 0, 240, 240, a1, a2);
			}
			if (command == FIConst.G_FILL_POLIGON) {
				// 三角形描画
				int[] xs = { 0, FIConst.CHIP, 0 };
				int[] ys = { 0, 0, FIConst.CHIP };
				for (int pt = 0; pt < 3; pt++) {
					if (pt == (a1)) {
						xs[pt] = FIConst.CHIP;
						ys[pt] = FIConst.CHIP;
					}
				}
				g.fillPolygon(xs, ys, 3);
			}
			if (command == FIConst.G_REPAINT) {
				fiLocal.useCache = false;
				Graphics2D gr = bg.createGraphics();
				fiLocal.paintAll(gr);
				canvas.repaint();
			}
			if (command == FIConst.G_FREE_IMAGE) {
				g.drawImage(chips, 0, a1, fiLocal.freeImage[2], a1
						+ fiLocal.freeImage[3], fiLocal.freeImage[0],
						fiLocal.freeImage[1], fiLocal.freeImage[0]
								+ fiLocal.freeImage[2], fiLocal.freeImage[1]
								+ fiLocal.freeImage[3], FIApplet.this);
			}
			return null;
		}

		@Override
		protected int g$fontWidth(Object g, String str) {
			return ((Graphics2D) g).getFontMetrics().stringWidth(str);
		}

		@Override
		protected int g$fontHeight() {
			return 11;
		}

		@Override
		protected void loadImage() throws Exception {
			String fname = "chips.gif";
			if (imageFilter == 1) {
				fname = "chipsw.gif";
			}
			chips = ImageIO.read(r$load(fname));
		}

		@Override
		protected void g$setMapChip(int index) {
			if (index < 0) {
				mapChips.clear();
			} else {
				BufferedImage image = new BufferedImage(FIConst.CHIP,
						FIConst.CHIP, BufferedImage.TYPE_INT_RGB);
				mapChips.add(image);
			}
		}

		@Override
		protected int getKeypadState() {
			return keyState;
		}

		@Override
		protected void r$close() {
		}

		@Override
		protected void r$closeSave(DataOutputStream dos) throws Exception {
			byte[] temp = bos.toByteArray();
			for (int i = 0; i < temp.length; i++) {
				slot[i + savePos] = temp[i];
			}
			bos.close();
			dos.close();

			// セーブスロット欄にbase64データを書き込み
			String base64 = Base64.encode(slot);
			saveString = base64;
		}

		@Override
		protected boolean r$getVer() {
			try {
				resVersion = r$load(".ver").readChar();
			} catch (Exception e) {
				e.printStackTrace();
			}
			return true;
		}

		@Override
		protected DataInputStream r$load(String fname) throws Exception {
			InputStream ins = getClass().getResourceAsStream("/res/" + fname);
			return new DataInputStream(ins);
		}

		@Override
		protected DataInputStream r$loadSlot(int pos) throws Exception {
			if (!saveString.startsWith("!!no")) {
				byte[] tempSlot = Base64.decode(saveString);
				if (tempSlot.length != slot.length) {
					saveString = "!!no match slotSize inputSize= "
							+ tempSlot.length + " : saveSize=" + slot.length;
				} else {
					slot = tempSlot;
				}
			}
			ByteArrayInputStream bis = new ByteArrayInputStream(slot);
			bis.skip(pos);
			return new DataInputStream(bis);
		}

		@Override
		protected void r$open() throws Exception {

		}

		@Override
		protected DataOutputStream r$saveSlot(int pos) throws Exception {
			savePos = pos;
			bos = new ByteArrayOutputStream();
			return new DataOutputStream(bos);
		}

		@Override
		protected void setSoftLabel(int key, String label) {

		}

	}

	@Override
	public void mouseClicked(MouseEvent e) {
		canvas.setFocusable(true);
		if (!timer.isRunning()) {
			timer.start();
		}
		if(e.getClickCount() >= 2){
			saveDialog.setSaveString(saveString);
			saveDialog.setVisible(true);
			saveString = saveDialog.getSaveString();
			System.out.println(saveString);
		}
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {

	}

	@Override
	public void mouseExited(MouseEvent arg0) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
	}

	@Override
	public void mouseReleased(MouseEvent arg0) {
	}
}
